package com.dukk.gis.tools;

import org.locationtech.jts.algorithm.distance.DistanceToPoint;
import org.locationtech.jts.algorithm.distance.PointPairDistance;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.linearref.LinearLocation;
import org.locationtech.jts.linearref.LocationIndexedLine;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * 线相关计算类
 */
public class LineTool {

    static GeometryFactory GEOMETRY_FACTORY = GeoTool.getGeoFactory();

    /**
     * 获取点到线的垂线
     * @param point
     * @param lineString
     * @return
     */
    public static Geometry verticalLine(Point point, Geometry lineString){

        PointPairDistance pointPairDistance = new PointPairDistance();

        DistanceToPoint.computeDistance(lineString, point.getCoordinate(),pointPairDistance);

        return GEOMETRY_FACTORY.createLineString(pointPairDistance.getCoordinates());

    }

    /**
     * 获取垂足
     * @param point
     * @param lineString
     * @return
     */
    public static Coordinate footPoint(Point point, Geometry lineString){

        Geometry geometry = verticalLine(point, lineString);
        Coordinate[] coordinates = geometry.getCoordinates();

        return coordinates[0];

    }


    /**
     * 截取l1在点coordinate0与coordinate1之间的线段
     * @param l1
     * @param coordinate0
     * @param coordinate1
     * @return
     */
    public static Geometry getExtractLine(Geometry l1, Coordinate coordinate0, Coordinate coordinate1){
        Coordinate[] coordinates = new Coordinate[2];
        coordinates[0] = coordinate0;
        coordinates[1] = coordinate1;
        return getExtractLine(l1, GEOMETRY_FACTORY.createLineString(coordinates));
    }


    /**
     * 获取l2在l1上面的线段
     * @param l1
     * @param l2
     * @return
     */
    public static Geometry getExtractLine(Geometry l1, Geometry l2){
        LocationIndexedLine locationIndexedLine = new LocationIndexedLine(l1);

        Coordinate p1 = l2.getCoordinates()[0];
        Coordinate Pn = l2.getCoordinates()[l2.getCoordinates().length-1];

        LinearLocation indexOfStart = locationIndexedLine.indexOf(p1);
        LinearLocation indexOfEnd = locationIndexedLine.indexOf(Pn);


        Geometry l11 = locationIndexedLine.extractLine(indexOfStart, indexOfEnd);

        return l11;
    }

    /**
     *
     * 获取l2在l1上面的线段，并借用l1距离垂足xm的形状点，没有的话返回垂足做起、终点
     *
     *
     * @param l1
     * @param l2
     * @param xm 单位米
     * @return
     */
    public static Geometry getExtractLine(Geometry l1, Geometry l2, double xm){

        double cmToDegree = DistanceTool.cmToDegree(xm*100);

        LocationIndexedLine locationIndexedLine = new LocationIndexedLine(l1);

        Coordinate[] coordinatesL1 = l1.getCoordinates();
        Coordinate p1 = coordinatesL1[0];
        Coordinate Pn = coordinatesL1[coordinatesL1.length-1];

        Coordinate[] coordinatesL2 = l2.getCoordinates();
        Coordinate l2_1 = coordinatesL2[0];
        Coordinate l2_n = coordinatesL2[coordinatesL2.length-1];

        //将l2的收尾点投影到 l1
        LinearLocation indexOfStart = locationIndexedLine.indexOf(l2_1);
        LinearLocation indexOfEnd = locationIndexedLine.indexOf(l2_n);

        //投影
        Geometry extractLine = locationIndexedLine.extractLine(indexOfStart, indexOfEnd);

        /*LocationIndexedLine locationIndexedLine = new LocationIndexedLine(l1);
        LinearLocation[] linearLocations = locationIndexedLine.indicesOf(l2);
        LinearLocation indexOfStart = linearLocations[0];
        LinearLocation indexOfEnd = linearLocations[1];
        Geometry extractLine = locationIndexedLine.extractLine(indexOfStart, indexOfEnd);*/


        if(indexOfStart.getSegmentIndex() == indexOfEnd.getSegmentIndex()){
            return  extractLine;
        }

        LineSegment lineSegmentStart = indexOfStart.getSegment(l1); //开始节点所在的索引片段
        LineSegment lineSegmentEnd = indexOfEnd.getSegment(l1); //终点所在的索引片段

        //1米节点计算
        Coordinate p0_start_1 =  lineSegmentStart.p0;
        Coordinate p0_start_2 =  lineSegmentStart.p1;

        Coordinate p0_end_1 =  lineSegmentEnd.p0;
        Coordinate p0_end_2 =  lineSegmentEnd.p1;


        //投影的起 终点
        Coordinate coordinate_s = indexOfStart.getCoordinate(l1);
        Coordinate coordinate_e = indexOfEnd.getCoordinate(l1);

        Coordinate[] coordinatesClone = extractLine.getCoordinates().clone();

        //起点重置
        if(coordinate_s.distance(p0_start_1) <= cmToDegree){
            coordinatesClone[0] = p0_start_1;
        }else if(coordinate_s.distance(p0_start_2) <= cmToDegree){
            coordinatesClone[0] = p0_start_2;
        }

        //终点重置
        if(coordinate_e.distance(p0_end_2) <= cmToDegree){
            coordinatesClone[coordinatesClone.length-1] = p0_end_2;
        }else if(coordinate_e.distance(p0_end_1) <= cmToDegree){
            coordinatesClone[coordinatesClone.length-1] = p0_end_1;
        }


        Coordinate[] coordinateNoRepeated = CoordinateArrays.removeRepeatedPoints(coordinatesClone);
        if(coordinateNoRepeated.length == 1){ //点坐标
            return  extractLine;
        }

        return GEOMETRY_FACTORY.createLineString(coordinatesClone);

    }

    /**
     * 获取point在l1上面的位置点(索引位置)
     *
     *
     * @param l1 基础数据
     * @param point l1外部区域的一个点
     * @return
     */
    public static PointLocation getLinearLocation(Geometry l1, Geometry point){


        LocationIndexedLine locationIndexedLine = new LocationIndexedLine(l1);

        //将l2的收尾点投影到 l1
        LinearLocation indexOfStart = locationIndexedLine.indexOf(point.getCoordinate());

        Coordinate coordinate = locationIndexedLine.extractPoint(indexOfStart);

        PointLocation pointLocation = new PointLocation();
        pointLocation.setCoordinate(coordinate);
        pointLocation.setIndex(indexOfStart.getSegmentIndex());
        pointLocation.setSegmentFraction(indexOfStart.getSegmentFraction());

        return pointLocation;


    }


    /**
     * 获取 pointGeoList 在线 l1上面的投影线
     *
     * 应用场景:
     *  将外部点映射到线，然后将映射点穿成线
     *
     *
     * @param l1 被投影对象，基础数据， pointGeoList投影到l1
     * @param pointGeoList 投影对象 point
     * @return 如果计算完没有返回point.empty 如果一个点返回 point 多个点返回linestring
     */
    public static Geometry getExtractLineByPoints(Geometry l1, List<Geometry> pointGeoList){

        List<PointLocation> pointLocationList = new ArrayList<>();
        for(Geometry point : pointGeoList){
            PointLocation pointLocation = getLinearLocation(l1, point);
            pointLocationList.add(pointLocation);
        }

        pointLocationList.sort(Comparator.comparing(value -> (value.getIndex()+ value.getSegmentFraction())));


        List<Coordinate> coordinates = new ArrayList<>();
        for(PointLocation pointLocation : pointLocationList){
            coordinates.add(pointLocation.getCoordinate());
        }

        Coordinate[] coordinates1 = CoordinateArrays.removeRepeatedPoints(CoordinateArrays.toCoordinateArray(coordinates));

        if (coordinates1.length <= 0){
            return GEOMETRY_FACTORY.createPoint();
        }

        if(coordinates1.length == 1){
            return GEOMETRY_FACTORY.createPoint(coordinates1[0]);
        }

        LineString lineString = GEOMETRY_FACTORY.createLineString(coordinates1);

        return lineString;


    }

    /**
     * 通过点截取线，点可以是线上的形状点也可以是靠近线的点
     *
     * 线　------------------------------------------
     * 点　.        .     .      .       .           .
     *
     * 截取完的线段:
     *    .---------.-----.------.-------.-----------.
     *
     */
    public static List<Geometry> subLineUseCoordinates(Geometry lineString, Coordinate[] coordinates){

        List<LinearLocation> linearLocations = new ArrayList<>();

        LocationIndexedLine locationIndexedLine = new LocationIndexedLine(lineString);

        LinearLocation startIndex = locationIndexedLine.getStartIndex();
        LinearLocation endIndex = locationIndexedLine.getEndIndex();

        //加入线的收尾 偏移量
        linearLocations.add(startIndex);
        linearLocations.add(endIndex);

        //加入点的偏移量
        for (Coordinate coordinate : coordinates) {
            LinearLocation indexOfCoordinate = locationIndexedLine.indexOf(coordinate);
            linearLocations.add(indexOfCoordinate);
        }

        //排序
        linearLocations.sort(Comparator.comparingDouble(e-> e.getSegmentIndex()+e.getSegmentFraction()));

        List<Geometry> subGeos = new ArrayList<>();

        //截取
        for (int i= 0; i < linearLocations.size(); i++) {
            int next = i+1;
            if(next >= linearLocations.size()){
                break;
            }
            LinearLocation currentLinear = linearLocations.get(i);
            LinearLocation nextLinear = linearLocations.get(next);

            subGeos.add(locationIndexedLine.extractLine(currentLinear, nextLinear));
        }

        return subGeos;

    }


    /**
     * 当geometry两两形状点的跨度大于XM的时候往geometry里面加入形状点
     *
     * @param geometry
     * @param XM
     * @return  新构造的线对象
     * @throws Exception
     */
    public static Geometry sewing(Geometry geometry, int XM) throws Exception {
        CoordinateList coordinateResultList = new CoordinateList();
        Coordinate[] coordinates = geometry.getCoordinates();
        for(int i=0; i<coordinates.length-1; i++){
            int next = i+1;
            if(next > coordinates.length){
                break;
            }

            Coordinate coordinate1 = coordinates[i];
            Coordinate coordinate2 = coordinates[next];

            coordinateResultList.addAll(sewingBySelf(coordinate1, coordinate2, XM), false);
        }

        return GEOMETRY_FACTORY.createLineString(coordinateResultList.toCoordinateArray());
    }




    private static CoordinateList sewingBySelf(Coordinate coordinateStart, Coordinate coordinateEnd, int XM) throws Exception {
        double distance = coordinateStart.distance(coordinateEnd);

        if(DistanceTool.degreeToM(distance) >= XM * 100){

            Coordinate[] c2 = new Coordinate[2];
            c2[0] = coordinateStart;
            c2[1] = coordinateEnd;
            LineString l2 = GEOMETRY_FACTORY.createLineString(c2);

            Point center = l2.getCentroid();

            double z = PointTool.getPointZ(GEOMETRY_FACTORY.createLineString(c2), center);

            center.getCoordinate().setZ(z);

            CoordinateList coordinateListLeft = sewingBySelf(coordinateStart, center.getCoordinate(),XM);
            CoordinateList coordinateListRight = sewingBySelf(center.getCoordinate(), coordinateEnd,XM);

            coordinateListLeft.addAll(coordinateListRight, false);

            return coordinateListLeft;

        }else {
            CoordinateList coordinates = new CoordinateList();
            coordinates.add(coordinateStart);
            coordinates.add(coordinateEnd);

            return coordinates;
        }

    }



}
